﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace Common.DBFHelper
{
    public class DBFReader
    {
        struct DbfDefine_Header
        {

            /// <summary>
            /// 版本
            /// </summary>
            public char Version;

            /// <summary>
            /// 最近的更新日期，年，以2000年为 起始
            /// </summary>
            public char Year;

            /// <summary>
            /// 最近的更新日期，月
            /// </summary>
            public char Month;

            /// <summary>
            /// 最近的更新日期，日
            /// </summary>
            public char Day;

            /// <summary>
            /// 记录数
            /// </summary>
            public int RecordsCount;

            /// <summary>
            /// 文件头长度
            /// </summary>
            public short HeaderLength;

            /// <summary>
            /// 单条记录大小
            /// </summary>
            public short RecordLength;

            /// <summary>
            /// 保留字节
            /// </summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            public char[] Reserved;

        }

        struct DbfDefine_Column
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
            public string Name;

            public char Type;

            public int Offset;

            public char Size;

            public char Decimal;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 14)]
            public char[] Reserved;
        }

        private static uint HeaderMetaSize = (uint)Marshal.SizeOf(typeof(DbfDefine_Header));

        private static uint ColumnMetaSize = (uint)Marshal.SizeOf(typeof(DbfDefine_Column));

        public static void ReadHeader(byte[] bytes, out DbfHeader dbfHeader, out List<DbfColumn> dbfColumns)
        {
            dbfHeader = null;
            dbfColumns = null;

            if (bytes == null)
            {
                throw new ArgumentNullException("bytes");
            }
            else if (bytes.Length <= HeaderMetaSize)
            {
                throw new Exception(string.Format("文件长度（{0}字节）小于DBF文件头长度（{1}字节）", bytes.Length, HeaderMetaSize));
            }
            DbfDefine_Header header = StructHelper.BytesToStruct<DbfDefine_Header>(bytes, 0, HeaderMetaSize);

            dbfHeader = new DbfHeader()
            {
                DBFVersion = header.Version,
                HeaderLength = header.HeaderLength,
                RecordsCount = header.RecordsCount,
                RecordLength = header.RecordLength,
                UpdateTime = new DateTime(2000 + header.Year, header.Month, header.Day)
            };
            if (bytes.Length < dbfHeader.HeaderLength)
            {
                throw new Exception(string.Format("文件长度（{0}字节）小于DBF文件头长度（{1}字节）", bytes.Length, dbfHeader.HeaderLength));
            }

            int fieldLengthSum = 1;//每条记录的第一个字节为删除标志
            dbfColumns = new List<DbfColumn>();

            for (uint offset = HeaderMetaSize; offset < header.HeaderLength - 1; offset += ColumnMetaSize)
            {

                var column = Common.StructHelper.BytesToStruct<DbfDefine_Column>(bytes, offset, ColumnMetaSize);

                if (ValidateColumnDefine(column))
                {

                    fieldLengthSum += column.Size;


                    DbfColumn c = new DbfColumn()
                    {
                        Name = column.Name,
                        Size = column.Size,
                        Decimal = column.Decimal,
                        Offset = column.Offset,
                        Type = (DbfColumn.DbfColumnType)column.Type
                    };

                    dbfColumns.Add(c);


                    if (fieldLengthSum == header.RecordLength)
                    {
                        break;
                    }
                    else if (fieldLengthSum > header.RecordLength)
                    {
                        throw new Exception(string.Format("定义的字段的长度和（{0}字节）超过文件头中定义的单条记录大小（{1}字节）", fieldLengthSum, header.RecordLength));
                    }
                }
            }
        }

        public static void ReadRecords<TDBFRecord>(byte[] bytes, DbfHeader header, List<DbfColumn> dbfColumns, out List<TDBFRecord> records)
            where TDBFRecord : DBFRecordBase<TDBFRecord>
        {

            int offset = header.HeaderLength;


            int recordsCount = (bytes.Length - header.HeaderLength) / header.RecordLength;
            if (recordsCount != header.RecordsCount)
            {
                throw new Exception("");
            }
            if (header.RecordsCount == 0)
            {
                records = null;
                return;
            }
          
            Dictionary<string, DbfColumn> dic = dbfColumns.ToDictionary(c => c.Name);

            records = new List<TDBFRecord>(header.RecordsCount);
            for (; offset < bytes.Length; offset += header.RecordLength)
            {
                TDBFRecord record = DBFRecordBase<TDBFRecord>.ReadRecord(bytes, offset, dic);
                records.Add(record);
            }


            //records = ReadRecords<TDBFRecord>(bytes, header, dic, 1200);
        }








        #region 多线程读取
        /// <summary>
        /// 从byte数组中解析DBF记录
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="buffer"></param>
        /// <param name="recordsPerThread"></param>
        /// <returns></returns>
        private static List<T> ReadRecords<T>(byte[] buffer, DbfHeader header, Dictionary<string, DbfColumn> columnDic, int recordsPerThread = 1200) where T : DBFRecordBase<T>
        {

            int recordCount = header.RecordsCount;

            if (recordCount < 1)
                return null;
            // 开始解析记录
            T[] records = new T[recordCount];


            // 起始位置从数据区开始
            int offset = header.HeaderLength;
            List<Task> tasks = new List<Task>();
            int index = 0;

            while (offset < buffer.Length - 1)
            {
                int endOffset = Math.Min(offset + header.RecordLength * recordsPerThread, buffer.Length - 1);

                ReadRecordParameter<T> p = new ReadRecordParameter<T>()
                {
                    Buffer = buffer,
                    EndOffset = endOffset,
                    StartOffset = offset,
                    RecordLength = header.RecordLength,
                    RecordIndex = index,
                    Records = records,
                    Columns =columnDic
                };
                Task t = Task.Factory.StartNew((object obj) => { ReadRecords<T>(obj); }, p);
                tasks.Add(t);
                offset = endOffset;
                index += recordsPerThread;
            }
            Task.WaitAll(tasks.ToArray());
            return records.ToList();
        }
        private class ReadRecordParameter<T> where T : DBFRecordBase
        {
            public byte[] Buffer;
            public int StartOffset;
            public int EndOffset;
            public int RecordLength;
            public int RecordIndex;
            public T[] Records;
            public Dictionary<string, DbfColumn> Columns;
        }
        private static void ReadRecords<T>(object para) where T : DBFRecordBase
        {
            ReadRecords<T>(para as ReadRecordParameter<T>);
        }
        private static void ReadRecords<T>(ReadRecordParameter<T> para) where T : DBFRecordBase
        {
            ReadRecords<T>(para.Buffer, para.Records, para.StartOffset, para.EndOffset, para.RecordLength, para.RecordIndex,para.Columns);
        }
        private static void ReadRecords<T>(byte[] buffer, T[] records, int startOffset, int endOffset, int recordLength, int recordIndex, Dictionary<string, DbfColumn> columnDic) where T : DBFRecordBase
        {

            while (startOffset < endOffset)
            {
                T record = DBFRecordBase<T>.ReadRecord(buffer, startOffset, columnDic);
                records[recordIndex] = record;
                startOffset += recordLength;
                recordIndex++;
            }
        }
        #endregion





        private static bool ValidateColumnDefine(DbfDefine_Column column)
        {
            if (string.IsNullOrEmpty(column.Name))
                return false;
            if (column.Offset < 0)
                return false;
            if (column.Size <= 0)
                return false;
            if (column.Type <= 0)
                return false;
            return true;
        }


    }

}
